//
// Copyright (c) ZeroC, Inc. All rights reserved.
//

import { Ice } from "ice";
import { Test } from "./Test.js";
import { TestHelper } from "../../Common/TestHelper.js";

const test = TestHelper.test;

export class Client extends TestHelper {
    async allTests() {
        class Condition {
            value: boolean;
            constructor(value: boolean) {
                this.value = value;
            }
        }

        class SetCB {
            condition: Condition;
            expected: number;
            constructor(condition: Condition, expected: number) {
                this.condition = condition;
                this.expected = expected;
            }

            response(value: number) {
                if (value != this.expected) {
                    this.condition.value = false;
                }
            }
        }

        const communicator = this.communicator();
        const out = this.getWriter();

        const hold = new Test.HoldPrx(communicator, `hold:${this.getTestEndpoint()}`);
        const holdOneway = hold.ice_oneway();

        const holdSerialized = new Test.HoldPrx(communicator, `hold:${this.getTestEndpoint(1)}`);
        const holdSerializedOneway = holdSerialized.ice_oneway();

        out.write("changing state between active and hold rapidly... ");

        for (let i = 0; i < 100; ++i) {
            await hold.putOnHold(0);
        }
        for (let i = 0; i < 100; ++i) {
            await holdOneway.putOnHold(0);
        }
        for (let i = 0; i < 100; ++i) {
            await holdSerialized.putOnHold(0);
        }
        for (let i = 0; i < 1; ++i) {
            await holdSerializedOneway.putOnHold(0);
        }
        out.writeLine("ok");

        out.write("testing without serialize mode... ");
        {
            const condition = new Condition(true);
            let value = 0;
            let result;
            const results: Promise<void>[] = [];
            while (condition.value) {
                const cb = new SetCB(condition, value);
                result = hold.set(++value, value < 500 ? Math.floor(Math.random() * 5) : 0);
                results.push(
                    result.then(
                        (v) => {
                            cb.response(v);
                        },
                        () => {
                            // Ignore exception
                        },
                    ),
                );

                if (value % 100 === 0) {
                    while (true) {
                        if (result.isSent()) {
                            break;
                        } else if (result.isCompleted()) {
                            await result; // This should throw the failure if the call wasn't sent but done.
                            test(result.isSent());
                        }
                        await Ice.Promise.delay(10);
                    }
                }

                if (value > 10000) {
                    // Don't continue, it's possible that out-of-order dispatch doesn't occur
                    // after 100000 iterations and we don't want the test to last for too long
                    // when this occurs.
                    break;
                }
            }
            test(value > 10000 || !condition.value);
            while (true) {
                if (result.isSent()) {
                    break;
                } else if (result.isCompleted()) {
                    await result; // This should throw the failure if the call wasn't sent but done.
                    test(result.isSent());
                }
                await Ice.Promise.delay(10);
            }
            await Promise.all(results);
        }
        out.writeLine("ok");

        out.write("testing with serialize mode... ");
        {
            const condition = new Condition(true);
            let value = 0;
            let result;
            const results: Promise<void>[] = [];
            while (value < 3000 && condition.value) {
                const cb = new SetCB(condition, value);
                result = holdSerialized.set(++value, 0);
                results.push(
                    result.then(
                        (v) => {
                            cb.response(v);
                        },
                        () => {
                            // Ignore exceptions
                        },
                    ),
                );

                if (value % 100 === 0) {
                    while (true) {
                        if (result.isSent()) {
                            break;
                        } else if (result.isCompleted()) {
                            await result; // This should throw the failure if the call wasn't sent but done.
                            test(result.isSent());
                        }
                        await Ice.Promise.delay(10);
                    }
                }
            }
            await result;
            test(condition.value);

            for (let i = 0; i < 10000; ++i) {
                await holdSerializedOneway.setOneway(value + 1, value);
                ++value;
                if (i % 100 == 0) {
                    await holdSerializedOneway.putOnHold(1);
                }
            }
            await Promise.all(results);
        }
        out.writeLine("ok");

        out.write("testing serialization... ");
        {
            let value = 0;
            let result: Ice.AsyncResult<void> | null = null;
            const results: Promise<void>[] = [];
            await holdSerialized.set(value, 0);

            for (let i = 0; i < 10000; ++i) {
                // Create a new proxy for each request
                result = holdSerialized.ice_oneway().setOneway(value + 1, value);
                results.push(result);
                ++value;
                if (i % 100 === 0) {
                    while (true) {
                        if (result.isSent()) {
                            break;
                        } else if (result.isCompleted()) {
                            await result; // This should throw the failure if the call wasn't sent but done.
                            test(result.isSent());
                        }
                        await Ice.Promise.delay(10);
                    }
                    await holdSerialized.ice_ping(); // Ensure everything's dispatched.
                    const conn = await holdSerialized.ice_getConnection();
                    await conn.close();
                }
            }
            await Promise.all(results);
        }
        out.writeLine("ok");

        out.write("testing waitForHold... ");
        await hold.waitForHold();
        await hold.waitForHold();
        for (let i = 0; i < 1000; ++i) {
            await holdOneway.ice_ping();
            if (i % 20 == 0) {
                await hold.putOnHold(0);
            }
        }
        await hold.putOnHold(-1);
        await hold.ice_ping();
        await hold.putOnHold(-1);
        await hold.ice_ping();
        out.writeLine("ok");

        out.write("changing state to hold and shutting down server... ");
        await hold.shutdown();
        out.writeLine("ok");
    }

    async run(args: string[]) {
        let communicator: Ice.Communicator | null = null;
        try {
            const [properties] = this.createTestProperties(args);
            //
            // For this test, we want to disable retries.
            //
            properties.setProperty("Ice.RetryIntervals", "-1");

            //
            // We don't want connection warnings because of the timeout
            //
            properties.setProperty("Ice.Warn.Connections", "0");

            [communicator] = this.initialize(properties);
            await this.allTests();
        } finally {
            if (communicator) {
                await communicator.destroy();
            }
        }
    }
}
